package service

import (
	"encoding/json"
	"errors"
	"github.com/jacktea/z-cron/clock"
	"github.com/jacktea/z-cron/log"
	"github.com/jacktea/z-cron/utils"
	"sync"
	"time"
)

type TaskPool struct {
	sync.Mutex
	clock    *clock.Clock
	cache    map[int64]*task // 任务缓存
	appCache map[string]*appCache
}
type appCache struct {
	data map[string]*task
}

func newAppCache() *appCache {
	return &appCache{
		data: make(map[string]*task),
	}
}

func (c *appCache) put(tokenId string, t *task) {
	c.data[tokenId] = t
}
func (c *appCache) remove(tokenId string) {
	delete(c.data, tokenId)
}

func (c *appCache) get(tokenId string) (*task, bool) {
	id, ok := c.data[tokenId]
	return id, ok
}

type task struct {
	id     int64
	appId  string
	taskId string
	notify string
	spec   string // cron表达式
	job    clock.Job
}

type TaskInfo struct {
	Id         int64     `json:"id"`
	AppId      string    `json:"appId"`
	TaskId     string    `json:"taskId"`
	Notify     string    `json:"notify"`
	Spec       string    `json:"spec"`
	Count      uint64    `json:"count"`
	CreateTime time.Time `json:"createTime"`
	ActionTime time.Time `json:"actionTime"`
}

func (t *task) doNotify() bool {
	if t.notify != "" {
		if body, err := json.Marshal(map[string]interface{}{
			"id":         t.id,
			"appId":      t.appId,
			"taskId":     t.taskId,
			"count":      t.job.Count(),
			"createTime": t.job.GetCreateTime(),
			"actionTime": t.job.GetActionTime(),
		}); err == nil {
			contentType := "application/json"
			if res, err := utils.HttpPostBody(t.notify, contentType, body); err != nil {
				return false
			} else {
				str := string(res)
				return str == "success"
			}
		} else {
			log.Errorf("json Marshal error %v", err)
		}
	}
	return true
}

func NewTaskPool() *TaskPool {
	return &TaskPool{
		clock:    clock.NewClock(),
		cache:    make(map[int64]*task),
		appCache: make(map[string]*appCache),
	}
}

func (p *TaskPool) getTask(appId, taskId string) (*task, bool) {
	if ac, ok := p.appCache[appId]; ok {
		return ac.get(taskId)
	} else {
		return nil, false
	}
}

func (p *TaskPool) deleteId(appId, taskId string) {
	if ac, ok := p.appCache[appId]; ok {
		ac.remove(taskId)
	}
}

func (p *TaskPool) mergeTaskWithDeadTime(appId, taskId, notify string, actionTime time.Time) (int64, error) {
	now := time.Now()
	if now.After(actionTime) {
		return 0, errors.New("time is out")
	}
	p.Lock()
	defer p.Unlock()
	if task, ok := p.getTask(appId, taskId); ok {
		if p.clock.UpdateJobDeadTime(task.job, actionTime) {
			return task.id, nil
		}
	}
	id := now.UnixNano() / 1000
	job, ok := p.clock.AddJobWithDeadTime(actionTime, func() bool {
		return p.runJob(id)
	}, func() bool {
		return p.RemoveTaskById(id)
	})
	if !ok {
		return 0, errors.New("add task fail")
	}
	task := &task{
		id:     id,
		appId:  appId,
		taskId: taskId,
		notify: notify,
		job:    job,
	}
	p.cache[id] = task
	//p.uCache[uId] = id
	if ac, ok := p.appCache[appId]; ok {
		ac.put(taskId, task)
	} else {
		ac = newAppCache()
		ac.put(taskId, task)
		p.appCache[appId] = ac
	}
	return id, nil
}

func (p *TaskPool) mergeCronTask(appId, taskId, notify, spec string) (int64, error) {
	now := time.Now()
	p.Lock()
	defer p.Unlock()
	if task, ok := p.getTask(appId, taskId); ok {
		if p.clock.UpdateCronJob(task.job, spec) {
			return task.id, nil
		}
	}
	id := now.UnixNano() / 1000
	job, ok := p.clock.AddCronJob(spec, func() bool {
		return p.runJob(id)
	}, func() bool {
		//return false
		return p.RemoveTaskById(id)
	})
	if !ok {
		return 0, errors.New("add task fail")
	}
	task := &task{
		id:     id,
		appId:  appId,
		taskId: taskId,
		notify: notify,
		spec:   spec,
		job:    job,
	}
	p.cache[id] = task
	//p.uCache[uId] = id
	if ac, ok := p.appCache[appId]; ok {
		ac.put(taskId, task)
	} else {
		ac = newAppCache()
		ac.put(taskId, task)
		p.appCache[appId] = ac
	}
	return id, nil
}

func (p *TaskPool) AddTask(appId, taskId, notify, spec string, actionTime time.Time) (int64, error) {
	if spec != "" {
		return p.mergeCronTask(appId, taskId, notify, spec)
	}
	return p.mergeTaskWithDeadTime(appId, taskId, notify, actionTime)
}

func (p *TaskPool) runJob(id int64) bool {
	p.Lock()
	task, ok := p.cache[id]
	p.Unlock()
	if ok {
		return task.doNotify()
	} else {
		return false
	}
}

func (p *TaskPool) RemoveTaskById(id int64) bool {
	p.Lock()
	defer p.Unlock()
	if task, ok := p.cache[id]; ok {
		delete(p.cache, id)
		p.deleteId(task.appId, task.taskId)
		task.job.Cancel()
		return true
	}
	return false
}

func (p *TaskPool) RemoveTask(appId, taskId string) bool {
	p.Lock()
	defer p.Unlock()
	if task, ok := p.getTask(appId, taskId); ok {
		//if task, ok := p.cache[id]; ok {
		delete(p.cache, task.id)
		p.deleteId(task.appId, task.taskId)
		task.job.Cancel()
		return true
		//}
	}
	return false
}

func (p *TaskPool) TaskExists(appId, taskId string) bool {
	p.Lock()
	defer p.Unlock()
	_, ok := p.getTask(appId, taskId)
	return ok
}

func (p *TaskPool) TaskCount() int {
	p.Lock()
	defer p.Unlock()
	return len(p.cache)
}

func (p *TaskPool) GetTaskById(id int64) (*TaskInfo, error) {
	p.Lock()
	defer p.Unlock()
	if task, ok := p.cache[id]; ok {
		return &TaskInfo{
			Id:         task.id,
			AppId:      task.appId,
			TaskId:     task.taskId,
			Notify:     task.notify,
			Spec:       task.spec,
			Count:      task.job.Count(),
			CreateTime: task.job.GetCreateTime(),
			ActionTime: task.job.GetActionTime(),
		}, nil
	} else {
		return nil, errors.New("task not exist")
	}
}

func (p *TaskPool) GetTask(appId, taskId string) (*TaskInfo, error) {
	p.Lock()
	defer p.Unlock()
	if task, ok := p.getTask(appId, taskId); ok {
		//if task, ok := p.cache[id]; ok {
		task.job.Count()
		return &TaskInfo{
			Id:         task.id,
			AppId:      task.appId,
			TaskId:     task.taskId,
			Notify:     task.notify,
			Spec:       task.spec,
			Count:      task.job.Count(),
			CreateTime: task.job.GetCreateTime(),
			ActionTime: task.job.GetActionTime(),
		}, nil
		//}
	}
	return nil, errors.New("task not exist")
}
func (p *TaskPool) GetTaskList(appId string) ([]*TaskInfo, error) {
	p.Lock()
	defer p.Unlock()
	arr := make([]*TaskInfo, 0)
	if ac, ok := p.appCache[appId]; ok {
		for _, task := range ac.data {
			info := &TaskInfo{
				Id:         task.id,
				AppId:      task.appId,
				TaskId:     task.taskId,
				Notify:     task.notify,
				Spec:       task.spec,
				Count:      task.job.Count(),
				CreateTime: task.job.GetCreateTime(),
				ActionTime: task.job.GetActionTime(),
			}
			arr = append(arr, info)
		}
	}
	return arr, nil
}
